#include "myproxy.h"

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// ******************************************************************************************
//  Function may protected by mutex
// ******************************************************************************************

char is_cached(char* file_name) {
#ifdef __MULTI_THREAD__
	//pthread_mutex_init(&mutex, NULL);
	pthread_mutex_lock(&mutex);
#endif
	FILE* data_file = fopen(file_name, "r");

	if (data_file == NULL) {
#ifdef __MULTI_THREAD__
		pthread_mutex_unlock(&mutex);
#endif
		return FALSE;
	} else {
		fclose(data_file);
#ifdef __MULTI_THREAD__
		pthread_mutex_unlock(&mutex);
#endif
		return TRUE;
	}
}

int cache_response(char* file_name, char* data, int data_length, char* write_mode){
	FILE* data_file;
	int item_written = -1;

#ifdef __MULTI_THREAD__
	//pthread_mutex_init(&mutex, NULL);
	pthread_mutex_lock(&mutex);
#endif

	if((data_file = fopen(file_name, write_mode)) == NULL){
		perror("Could not open the cache-file for writing.\n");
	}else {
		printf("Proxy> Caching response to file: %s\n", file_name);
		item_written = fwrite(data, data_length, 1, data_file);
		fclose(data_file);
	}
#ifdef __MULTI_THREAD__
	pthread_mutex_unlock(&mutex);
#endif
	
	return item_written;
}


// Case 1 - No If-Modified-Since and no Cache-Control.
// Case 2b - With If-Modified-Since and no Cache-Control. (Request IMS <= cached data last modify time)
void case1_2b_request_process(struct struct_request_identity *requestIdentity) {
	FILE* data_file;
	int data_file_size;
	char data_file_size_string[10];
	char* response_header = calloc(1, MAX_HEADER_SIZE);
	char* response_payload;
	char content_encoding[20] = {'\0'};

#ifdef __MULTI_THREAD__
	//pthread_mutex_init(&mutex, NULL);
	pthread_mutex_lock(&mutex);
#endif
	if((data_file = fopen(requestIdentity->file_name, "rb")) == NULL){
		perror("Could not open cache-file for reading\n");
#ifdef __MULTI_THREAD__
		pthread_mutex_unlock(&mutex);
#endif
		free(response_header);
		return;
	}

	// Find the file size
	fseek(data_file, 0, SEEK_END);
	data_file_size = (int) ftell(data_file) - 20;
	fseek(data_file, 0, SEEK_SET);
	sprintf(data_file_size_string, "%d", data_file_size);

	//First 20 bytes of cache-file contain either the content encoding string or null-characters
	fread(content_encoding, 20, 1, data_file);
	printf("Read Content-Encoding: %s from cached file...\n", content_encoding);
	
//	printf("data file size %d\n", data_file_size);
	// Prepare the message payload
	response_payload = calloc(1, data_file_size);
	fread(response_payload, data_file_size, 1, data_file);
	fclose(data_file);
#ifdef __MULTI_THREAD__
	pthread_mutex_unlock(&mutex);
#endif

	// Construct HTTP 200 header
	response_header = strncat(response_header, requestIdentity->connection_type, strlen(requestIdentity->connection_type));
	response_header = strcat(response_header, " 200 OK\r\n");
	

	response_header = strcat(response_header, "Connection: close\r\n");
	

	//Add content encoding
	if (strlen(response_payload) != data_file_size){
		if(strlen(content_encoding) > 0){
			response_header = strcat(response_header, "Content-Encoding: ");
			response_header = strcat(response_header, content_encoding);
			response_header = strcat(response_header, "\r\n");
		}
	}

	response_header = strcat(response_header, "Content-Length: ");
	response_header = strncat(response_header, data_file_size_string, strlen(data_file_size_string));
	
	response_header = strcat(response_header, "\r\n\r\n");
	printf("Case 1 header:\n<%s>\n", response_header);
	// Send the header to client
	sendn(requestIdentity->client_socket_descriptor, response_header, strlen(response_header));
	

	// Send the payload to client
	sendn(requestIdentity->client_socket_descriptor, response_payload, data_file_size);
	
	free(response_header);
	free(response_payload);
}

// ******************************************************************************************










int month_to_int(char* month_string){
	if(strcmp(month_string, "Jan") == 0){
		return 1;
	}
	if(strcmp(month_string, "Feb") == 0){
		return 2;
	}
	if(strcmp(month_string, "Mar") == 0){
		return 3;
	}
	if(strcmp(month_string, "Apr") == 0){
		return 4;
	}
	if(strcmp(month_string, "May") == 0){
		return 5;
	}
	if(strcmp(month_string, "Jun") == 0){
		return 6;
	}
	if(strcmp(month_string, "Jul") == 0){
		return 7;
	}
	if(strcmp(month_string, "Aug") == 0){
		return 8;
	}	
	if(strcmp(month_string, "Sep") == 0){
		return 9;
	}
	if(strcmp(month_string, "Oct") == 0){
		return 10;
	}
	if(strcmp(month_string, "Nov") == 0){
		return 11;
	}
	if(strcmp(month_string, "Dec") == 0){
		return 12;
	}	
	else{
		return -1;
	}
}

int not_modified_since(char* cache_time, char* req_time){
	char* cache_ims = calloc(1, strlen(cache_time) + 1);
	char* req_ims = calloc(1, strlen(req_time) + 1);
	
	int cache_day;
	int cache_month;
	int cache_year;
	int cache_hour;	
	int cache_minute;
	int cache_second;
	
	int req_day;
	int req_month;
	int req_year;
	int req_hour;	
	int req_minute;
	int req_second;	
	
	strcpy(cache_ims, cache_time);
	strcpy(req_ims, req_time);
	
	cache_day = atoi(strtok(cache_ims, " "));
	cache_month = atoi(strtok(NULL, " "));
	cache_year = atoi(strtok(NULL, " "));
	cache_hour = atoi(strtok(NULL, " "));	
	cache_minute = atoi(strtok(NULL, " "));
	cache_second = atoi(strtok(NULL, " "));

	printf("%s\n", req_ims);
	req_day = atoi(strtok(req_ims, " "));
	req_month = month_to_int(strtok(NULL, " "));
	req_year = atoi(strtok(NULL, " ") + 2);//EEGH, use just 2 last digits...
	req_hour = atoi(strtok(NULL, ":"));	
	req_minute = atoi(strtok(NULL, ":"));
	req_second = atoi(strtok(NULL, "\0"));
	
	free(cache_ims);
	free(req_ims);
	
	if(cache_year < req_year){
		return 0;
	}else if(cache_year == req_year){
		if(cache_month < req_month){
			return 0;
		}else if(cache_month == req_month){
			if(cache_day < req_day){
				return 0;
			}else if(cache_day == req_day){
				if(cache_hour * 3600 + cache_minute * 60 + cache_second < 
					req_hour * 3600 + req_minute * 60 + req_second){
					return 0;
				}else{
					return 1;
				}
			}
		}
	}
	return 1;
}

void strrep(char *str, char old, char new)  {
    char *pos;
    while (1)  {
        pos = strchr(str, old);
        if (pos == NULL)  {
            break;
        }
        *pos = new;
    }
}

time_t to_time_t(char* time_string) {
	struct tm buf;
	time_t ims;
	// Sample IMS: If-Modified-Since: Wed, 11 Aug 2010 14:49:20 GMT

	//strptime(time_string, "%a, %d %B %Y %T GMT", &buf);
	printf("IMS Original time string -> %s\n", time_string);
	
	strptime(time_string, "%a, %d %b %Y %X GMT", &buf);
	
	printf("IMS time string in GMT -> %s\n", time_string);
	
	ims = mktime(&buf);
	
	
	printf("ims-> %s\n", (const char*) ctime(&ims));
	return ims;
}

char* to_time_string(time_t *time_input, char *time_string){
	/*char *saveptr;
	char temp_time_string[100] = {'\0'};
	char buffer_month[5] = {'\0'};
	char buffer_time[20] = {'\0'};
	char* time_string_buffer;
	
	strcpy(temp_time_string, (const char*) ctime(time_input));

	// Sample IMS: If-Modified-Since: Wed, 11 Aug 2010 14:49:20 GMT
	// ctime output sample          : Wed Mar 16 22:48:48 2011
	
	
	
	// Copy weekday
	time_string = strcat(time_string, strtok_r(temp_time_string, " ", &saveptr));
	time_string = strcat(time_string, ", ");
	
	// Buffer month
	strcat(buffer_month, strtok_r(NULL, " ", &saveptr));

	//printf("time_string: %s\n", temp_time_string);
	// Copy day
	time_string = strcat(time_string, strtok_r(NULL, " ", &saveptr));
	time_string = strcat(time_string, " ");

	//printf("time_string: %s\n", temp_time_string);
	// Copy month
	time_string = strncat(time_string, buffer_month, sizeof(buffer_month));
	time_string = strcat(time_string, " ");

	// Buffer time
	time_string_buffer = strtok_r(NULL, " ", &saveptr);
	strcat(buffer_time, time_string_buffer);
	

	// Copy year
	time_string = strcat(time_string, strtok_r(NULL, " \r\n", &saveptr));
	time_string = strcat(time_string, " ");

	// Copy time
	time_string = strncat(time_string, buffer_time, sizeof(buffer_time));
	time_string = strcat(time_string, " GMT");
	*/
	
	strftime(time_string, TIME_LENGTH, "%a, %d %b %Y %X %Z", gmtime(time_input));
	printf("time_string output: %s\n", time_string);
	
	return time_string;
}


time_t find_cache_last_modified_time(char* path){
	struct stat buf;
	time_t lmt;
	
	if ((stat(path, &buf)) != 0) {
		if(errno != ENOENT) {
			perror("Error");
		} else {
			// Cached not exists
		}
	} else {
		memcpy(&lmt, &buf.st_mtime, sizeof(time_t));
	}
	
	printf("lmt-> %s\n", (const char*) ctime(&lmt));
	return lmt;
}


char* generate_filename(char* req_host, char* req_relative_path, short port) {
	char url[MAX_ADDR_SIZE] = {'\0'};
	char port_string[6] = {'\0'};
	char* result_file_name;

	memset(url, '\0', MAX_ADDR_SIZE);
	memset(port_string, '\0', 6);
	
	sprintf(port_string, "%hd", port);
	strcat(url, "http://");
	strcat(url, req_host);
	strcat(url, ":");
	strcat(url, port_string);
	strcat(url, req_relative_path);
	
	result_file_name = crypt(url, "$1$00$");
	strrep(result_file_name, '/', '_');
	
	//printf("\033[1;31mProxy> File name for URL <%s> is <%s>\033[0m\n", url, result_file_name);
	return result_file_name;
}

int accept_connection(int proxy_socket_descriptor)
{
	struct sockaddr_in client_address;
	unsigned int client_addrlen;
	int client_socket_descriptor;

	client_addrlen = sizeof(struct sockaddr_in);
	client_socket_descriptor = accept(proxy_socket_descriptor, (struct sockaddr *)&client_address, &client_addrlen);

	printf("\033[1;31m******************************************************************\033[0m\n");
	printf("Proxy> Accepted new connection from socket %d\n", client_socket_descriptor);
	return client_socket_descriptor;
}

int create_and_bind_socket(short server_port) {
	struct sockaddr_in server_address;
	int socket_descriptor;
	int one = 1;
	
	// create socket
	if ((socket_descriptor = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("Cannot create socket");
		exit(-1);
	}
	else {
		setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(int));
		server_address.sin_family = AF_INET;
		server_address.sin_port = htons(server_port);
		server_address.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	
	// bind socket
	if (bind(socket_descriptor, (struct sockaddr *)&server_address, sizeof(struct sockaddr_in)) < 0) {
		perror("Cannot bind socket");
		exit(-1);
	}
	return socket_descriptor;
}

int main(int argc, char** argv) {
	short server_port;
	int proxy_socket_descriptor;
	pthread_t pid;

	// Ignore the SIGPIPE signal which cause the program terminate =.=
	signal(SIGPIPE, SIG_IGN);
	
	// Set the timezone
	putenv("TZ=GMT");
	tzset();
	
	// Argument check
	if (argc == 1) {
		fprintf(stderr, "Error: Port number missing.\n");
		exit(-1);
	}
	else {
		sscanf(argv[1], "%hd", &server_port);
	}

	proxy_socket_descriptor = create_and_bind_socket(server_port);
	listen(proxy_socket_descriptor, BACKLOG);

	printf("Proxy> Server started at port %s\n", argv[1]);

	while(1) {
#ifdef __MULTI_THREAD__
		pthread_create(&pid, NULL, (void*) receive_and_process_request_thread, (void*) accept_connection(proxy_socket_descriptor));
#endif

#ifndef __MULTI_THREAD__
		receive_and_process_request_thread(accept_connection(proxy_socket_descriptor));
#endif
	}

	return 0;
}